#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <getopt.h>
#include <pthread.h>
#include <fcntl.h>


#include "../../utils.h"
#include "../../capture.h"

#define OUTPUT_PLUGIN_NAME "UDP output plugin"
#define MAX_ARGUMENTS 30

extern GLOBALS global;

static pthread_t worker;
static char *command = NULL;

static struct sockaddr_in addr;
static int sd;
static char recvBuf[1024]={0};

#define DEFAULT_PORT = 8080
#define MAX_UDP_CONNECTION 4
#define BYTES_PER_SEND	1300

static pthread_t udp_client_a[MAX_UDP_CONNECTION];
static unsigned char currentConnectionsNum = 0;
static unsigned char *frame = NULL;
static unsigned int frame_size = 0;

// UDP port
static int port = 0;


void help(void)
{
    fprintf(stderr, " ---------------------------------------------------------------\n" \
            " Help for output plugin..: "OUTPUT_PLUGIN_NAME"\n" \
            " ---------------------------------------------------------------\n" \
            " The following parameters can be passed to this plugin:\n\n" \
            " [-p | --port ]..........: UDP port to listen for picture requests. UDP message is the filename to save\n\n" \
            " ---------------------------------------------------------------\n");
}


void worker_cleanup(void *arg)
{
    static unsigned char first_run = 1;

    if(!first_run) {
        //DBG("already cleaned up ressources\n");
        return;
    }

    first_run = 0;
    fprintf(stderr,"cleaning up ressources allocated by worker thread\n");

    if(frame != NULL) {
        free(frame);
    }
}

static void Init_sock()
{
  // set UDP server data structures ---------------------------
    if(port <= 0) 
    {
        fprintf(stderr,"a valid UDP port must be provided\n");
        exit(EXIT_FAILURE);
    }
    sd = socket(PF_INET, SOCK_DGRAM, 0);
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(port);
    if(bind(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0)
        perror("bind");
    // -----------------------------------------------------------
}

static int SendFrame( struct sockaddr * clientAddr)
{
    unsigned char * tmp;
    tmp = frame;
    size_t cnt = 0;
    size_t ret;
    
    while(cnt < frame_size)
    {
      ret = sendto(sd, tmp, BYTES_PER_SEND, 0, (struct sockaddr*)&addr, sizeof(addr));
      tmp += ret;
      cnt += ret;
    }
  return 0;
}
	  
void *udp_thread(void *clientAddr)
{
  while(!global.stopFlag) 
    {
        pthread_mutex_lock(&global.db);
        pthread_cond_wait(&global.db_update, &global.db);

        /* check if buffer for frame is large enough, increase it if necessary */
        if(global.bufSize > frame_size) 
	{
	    frame_size = global.bufSize + 30720; 	//分配的字节数， 比当前buf需求的大 30KB 字节， 防止频繁内存分配释放。
	    unsigned char * tmp_framebuffer = NULL;
            if((tmp_framebuffer = (unsigned char *)realloc(frame, frame_size)) == NULL) 
	    {
                pthread_mutex_unlock(&global.db);
                fprintf(stderr,"not enough memory!!!\n");
                exit(EXIT_FAILURE);
            }
            frame = tmp_framebuffer;
        }

        /* copy frame to our local buffer now */
        memcpy(frame, global.buf, global.bufSize);

        /* allow others to access the global buffer again */
        pthread_mutex_unlock(&global.db);
        
	
        // send back client's message that came in udpbuffer
        //sendto(sd, udpbuffer, bytes, 0, (struct sockaddr*)&addr, sizeof(addr));
	SendFrame(clientAddr);
	
    }

    free(clientAddr);
    
    return NULL;
}

void *worker_thread(void *arg)
{
    /* set cleanup handler to cleanup allocated ressources */
    unsigned int addr_len = sizeof(addr);
    
    pthread_cleanup_push(worker_cleanup, NULL);
  
    while(!global.stopFlag)
    {
      recvfrom(sd, recvBuf, sizeof(recvBuf), 0, (struct sockaddr*)&addr, &addr_len);
      /*
       strcmp(recvBuf, "command");
       //判断命令还是新连接
       */
      if(currentConnectionsNum <= 3)
      {	  
	  unsigned char *tmpAddr = (unsigned char *)malloc( sizeof(addr) );
	  memcpy(tmpAddr, &addr, sizeof(addr) );
	  pthread_create(&udp_client_a[currentConnectionsNum], 0, udp_thread, tmpAddr);
	  currentConnectionsNum++;		//数组下标 比 计数值小1
      }
      else
      {
	 sendto(sd, "touch the connections limits\n", strlen("touch the connections limits\n") + 1, 0, (struct sockaddr*)&addr, sizeof(addr));
      }
    }
    
    // close UDP port
    if(port > 0)
        close(sd);

    /* cleanup now */
    pthread_cleanup_pop(1); 
    return NULL;
}

static void Parse_cmdLine(char *outputParam)
{
    int i;
    char *argv[MAX_ARGUMENTS]={NULL, };
    int argc=1;
   
    char *str =strchr(outputParam, ' ');
    // convert the single parameter-string to an array of strings 
    argv[0] = OUTPUT_PLUGIN_NAME;
    if ( str != NULL && strlen(str) != 0 ) {
        char *arg=NULL, *saveptr=NULL, *token=NULL;

        arg=(char *)strdup(str);
    
        if ( strchr(arg, ' ') != NULL ) {
            token=strtok_r(arg, " ", &saveptr);
            if ( token != NULL ) {
                argv[argc] = strdup(token);
                argc++;
                while ( (token=strtok_r(NULL, " ", &saveptr)) != NULL ) {
                    argv[argc] = strdup(token);
                    argc++;
                    if (argc >= MAX_ARGUMENTS) {
                        fprintf(stderr, "ERROR: too many arguments to input plugin\n");
                        exit(EXIT_FAILURE);
                    }
                }
            }
        }
    }

    /* show all parameters for DBG purposes */
    for(i = 0; i < argc; i++) {
        //DBG("argv[%d]=%s\n", i, argv[i]);
    }

    reset_getopt();
    while(1) {
        int option_index = 0, c = 0;
        static struct option long_options[] = {
            {"h", no_argument, 0, 0},
            {"help", no_argument, 0, 0},
            {"c", required_argument, 0, 0},
            {"command", required_argument, 0, 0},
            {"p", required_argument, 0, 0},
            {"port", required_argument, 0, 0},
            {0, 0, 0, 0}
        };

        c = getopt_long_only(argc, argv, "", long_options, &option_index);

        /* no more options to parse */
        if(c == -1) break;

        /* unrecognized option */
        if(c == '?') {
            help();
            exit(EXIT_FAILURE);
        }

        switch(option_index) {
            /* h, help */
        case 0:
        case 1:
            //DBG("case 0,1\n");
            help();
            exit(EXIT_SUCCESS);
            break;

            /* c, command */
        case 2:
        case 3:
            //DBG("case 6,7\n");
            command = strdup(optarg);
            break;
            /* p, port */
        case 4:
        case 5:
            //DBG("case 8,9\n");
            port = atoi(optarg);
            break;            
        }
    }
}

int output_init(char *outputParam)
{    
    Parse_cmdLine(outputParam);
    
    Init_sock();
    
    return 0;
}


int output_stop(int id)
{
    pthread_cancel(worker);
    return 0;
}


int output_run(int id)
{
    pthread_create(&worker, 0, worker_thread, NULL);
    pthread_detach(worker);
    return 0;
}


